package cn.yunbamboos.rest.service.invoke;

import cn.hutool.core.io.FileUtil;
import cn.yunbamboos.rest.service.dto.in.BaseInDTO;
import cn.yunbamboos.rest.service.dto.out.BaseOutDTO;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

public class GenerateBeanInvoke {

    private static final Logger logger = LoggerFactory.getLogger(GenerateBeanInvoke.class);

    // 接口名称
    private final Class<?> interfaceClass;
    private final String methodName;
    private final Class<? extends BaseInDTO> inClass;
    private final Class<? extends BaseOutDTO> outClass;
    private final String returnType;
    private final boolean enableFile;
    private final Package pack;
    private final String packagePath;
    private final String className;

    public GenerateBeanInvoke(Class<?> interfaceClass, String methodName, Class<? extends BaseInDTO> inClass, Class<? extends BaseOutDTO> outClass, String returnType, boolean enableFile) {
        this.interfaceClass = interfaceClass;
        this.methodName = methodName;
        this.inClass = inClass;
        this.outClass = outClass;
        this.returnType = returnType;
        this.enableFile = enableFile;
        this.pack = interfaceClass.getPackage();
        this.packagePath = pack.getName().replace(".", "/");
        this.className = interfaceClass.getSimpleName() + "_" + methodName;
    }


    public byte[] generate() {
        String classType = packagePath + "/" + className;
        String interfaceType = Type.getInternalName(interfaceClass);
        String inType = Type.getInternalName(inClass);
        String outType = Type.getInternalName(outClass);

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;

        cw.visit(Opcodes.V21, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, classType, null, "java/lang/Object", new String[]{"cn/yunbamboos/rest/service/IBeanInvoke"});

        cw.visitSource("ILoginService_login.java", null);

        {
            fieldVisitor = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "bean", "Ljava/lang/Object;", null, null);
            fieldVisitor.visitEnd();
        }
        {
            methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Ljava/lang/Object;)V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(12, label0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(13, label1);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, classType, "bean", "Ljava/lang/Object;");
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(14, label2);
            methodVisitor.visitInsn(Opcodes.RETURN);
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLocalVariable("this", "L"+classType+";", null, label0, label3, 0);
            methodVisitor.visitLocalVariable("bean", "Ljava/lang/Object;", null, label0, label3, 1);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "invoke", "(Lcn/yunbamboos/rest/service/dto/in/BaseInDTO;)Ljava/lang/Object;", null, new String[]{"java/lang/Exception"});
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(18, label0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classType, "bean", "Ljava/lang/Object;");
            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, interfaceType);
            methodVisitor.visitVarInsn(Opcodes.ASTORE, 2);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(19, label1);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, inType);
            methodVisitor.visitVarInsn(Opcodes.ASTORE, 3);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(20, label2);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfaceType, methodName, "(L"+inType+";)L"+returnType+";", true);
            methodVisitor.visitInsn(Opcodes.ARETURN);
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLocalVariable("this", "L"+classType+";", null, label0, label3, 0);
            methodVisitor.visitLocalVariable("baseInDTO", "Lcn/yunbamboos/rest/service/dto/in/BaseInDTO;", null, label0, label3, 1);
            methodVisitor.visitLocalVariable("service", "L"+interfaceType+";", null, label1, label3, 2);
            methodVisitor.visitLocalVariable("in", "L"+inType+";", null, label2, label3, 3);
            methodVisitor.visitMaxs(2, 4);
            methodVisitor.visitEnd();
        }
        cw.visitEnd();

        byte[] bytes = cw.toByteArray();

        if (enableFile) {
            try {
                String fileDir = System.getProperty("user.dir") + File.separator + "target" + File.separator + pack.getName().replace(".", File.separator) + File.separator;
                FileUtil.mkdir(fileDir);
                String filePath = fileDir + File.separator + className + ".class";
                if (logger.isTraceEnabled()) {
                    logger.trace("write class file {}", filePath);
                }
                FileUtil.writeBytes(bytes, filePath);
            } catch (Exception e) {
                logger.error("write class file error", e);
            }
        }
        return bytes;
    }

    public String getPackagePath() {
        return packagePath;
    }

    public String getClassName() {
        return className;
    }

}
